/***********************************************************************
    filename:   CEGUIRenderedStringParser.cpp
    created:    28/05/2009
    author:     Paul Turner
 *************************************************************************/

#include "hgeGuiRenderedString.h"
#include "hgeGuiRenderedStringParser.h"
#include "hgeGuiRenderedStringComponent.h"
#include "hgeLogger.h"
#include "hgeFont.h"
#include "hgePropertyHelper.h"

//----------------------------------------------------------------------------//
const String RenderedStringParser::ColourTagName("colour");
const String RenderedStringParser::FontTagName("font");
const String RenderedStringParser::ImageTagName("image");
const String RenderedStringParser::WindowTagName("window");
const String RenderedStringParser::VertAlignmentTagName("vert-alignment");
const String RenderedStringParser::PaddingTagName("padding");
const String RenderedStringParser::TopPaddingTagName("top-padding");
const String RenderedStringParser::BottomPaddingTagName("bottom-padding");
const String RenderedStringParser::LeftPaddingTagName("left-padding");
const String RenderedStringParser::RightPaddingTagName("right-padding");
const String RenderedStringParser::AspectLockTagName("aspect-lock");
const String RenderedStringParser::ImageSizeTagName("image-size");
const String RenderedStringParser::ImageWidthTagName("image-width");
const String RenderedStringParser::ImageHeightTagName("image-height");
const String RenderedStringParser::TopAlignedValueName("top");
const String RenderedStringParser::BottomAlignedValueName("bottom");
const String RenderedStringParser::CentreAlignedValueName("centre");
const String RenderedStringParser::StretchAlignedValueName("stretch");

//----------------------------------------------------------------------------//
RenderedStringParser::RenderedStringParser() :
    d_initialColours(0xFFFFFFFF),
    d_vertAlignment(VF_BOTTOM_ALIGNED),
    d_imageSize(0, 0),
    d_aspectLock(false),
    d_initialised(false)
{
    initialiseDefaultState();
}

//----------------------------------------------------------------------------//
RenderedStringParser::RenderedStringParser(const String& initial_font,
                                            const ColourRect& initial_colours) :
    d_initialFontName(initial_font),
    d_initialColours(initial_colours),
    d_vertAlignment(VF_BOTTOM_ALIGNED),
    d_imageSize(0, 0),
    d_aspectLock(false),
    d_initialised(false)
{
    initialiseDefaultState();
}

//----------------------------------------------------------------------------//
RenderedStringParser::~RenderedStringParser()
{
}

//----------------------------------------------------------------------------//
RenderedString RenderedStringParser::parse(const String& input_string,
                                                hgeFont* initial_font,
                                                const ColourRect* initial_colours)
{
    // first-time initialisation (due to issues with static creation order)
    if (!d_initialised)
        initialiseTagHandlers();

    initialiseDefaultState();

    // maybe override initial font.
    if (initial_font)
        d_fontName = initial_font->getName();

    // maybe override initial colours.
    if (initial_colours)
        d_colours = *initial_colours;

    RenderedString rs;
    String curr_section;

    size_t curr_pos = 0;

    while (curr_pos < input_string.length())
    {
        size_t cstart_pos = input_string.find_first_of('[', curr_pos);

        // if no control sequence start char, add remaining text verbatim.
        if (String::npos == cstart_pos)
        {
            curr_section += input_string.substr(curr_pos);
            curr_pos = input_string.length();
        }
        else if (cstart_pos == curr_pos || input_string[cstart_pos - 1] != '\\')
        {
            // append everything up to the control start to curr_section.
            curr_section += input_string.substr(curr_pos, cstart_pos - curr_pos);

            // scan forward for end of control sequence
            size_t cend_pos = input_string.find_first_of(']', cstart_pos);
            // if not found, treat as plain text
            if (String::npos == cend_pos)
            {
                curr_section += input_string.substr(curr_pos);
                curr_pos = input_string.length();
            }
            // extract control string
            else
            {
                appendRenderedText(rs, curr_section);
                curr_section.clear();

                String ctrl_string(
                    input_string.substr(cstart_pos + 1,
                                        cend_pos - cstart_pos - 1));
                curr_pos = cend_pos + 1;

                processControlString(rs, ctrl_string);
                continue;
            }
        }
        else
        {
            curr_section += input_string.substr(curr_pos,
                                                cstart_pos - curr_pos - 1);
            curr_section += '[';
            curr_pos = cstart_pos + 1;
            continue;
        }

        appendRenderedText(rs, curr_section);
        curr_section.clear();
    }

    return rs;
}

//----------------------------------------------------------------------------//
void RenderedStringParser::appendRenderedText(RenderedString& rs,
                                                   const String& text) const
{
    size_t cpos = 0;
    // split the given string into lines based upon the newline character
    while (text.length() > cpos)
    {
        // find next newline
        const size_t nlpos = text.find('\n', cpos);
        // calculate length of this substring
        const size_t len =
            ((nlpos != String::npos) ? nlpos : text.length()) - cpos;

        // construct new text component and append it.
        if (len > 0)
        {
            RenderedStringTextComponent rtc(text.substr(cpos, len), d_fontName);
            rtc.setPadding(d_padding);
            rtc.setColours(d_colours);
            rtc.setVerticalFormatting(d_vertAlignment);
            rtc.setAspectLock(d_aspectLock);
            rs.appendComponent(rtc);
        }

        // break line if needed
        if (nlpos != String::npos)
            rs.appendLineBreak();

        // advance current position.  +1 to skip the \n char
        cpos += len + 1;
    }
}

//----------------------------------------------------------------------------//
void RenderedStringParser::processControlString(RenderedString& rs,
                                                     const String& ctrl_str)
{
    // all our default strings are of the form <var> = <val>
    // so do a quick check for the = char and abort if it's not there.
    if (String::npos == ctrl_str.find('='))
    {
        hgeLogger::getSingleton().logEvent(
            "RenderedStringParser::processControlString: unable to make "
            "sense of control string '" + ctrl_str + "'.  Ignoring!");
        
        return;
    }

    char var_buf[128];
    char val_buf[128];
    sscanf(ctrl_str.c_str(), " %127[^ =] = '%127[^']", var_buf, val_buf);

    const String var_str(var_buf);
    const String val_str(val_buf);

    // look up handler function
    TagHandlerMap::iterator i = d_tagHandlers.find(var_str);
    // despatch handler, or log error
    if (i != d_tagHandlers.end())
        (this->*(*i).second)(rs, val_str);
    else
        hgeLogger::getSingleton().logEvent(
            "RenderedStringParser::processControlString: unknown "
            "control variable '" + var_str + "'.  Ignoring!");
}

//----------------------------------------------------------------------------//
void RenderedStringParser::initialiseDefaultState()
{
    d_padding = hgeRect(0, 0, 0, 0);
    d_colours = d_initialColours;
    d_fontName = d_initialFontName;
    d_imageSize.d_x = d_imageSize.d_y = 0.0f;
    d_vertAlignment = VF_BOTTOM_ALIGNED;
    d_aspectLock = false;
}

//----------------------------------------------------------------------------//
void RenderedStringParser::setInitialFontName(const String& font_name)
{
    d_initialFontName = font_name;
}

//----------------------------------------------------------------------------//
void RenderedStringParser::setInitialColours(const ColourRect& colours)
{
    d_initialColours = colours;
}

//----------------------------------------------------------------------------//
const String& RenderedStringParser::getInitialFontName() const
{
    return d_initialFontName;
}

//----------------------------------------------------------------------------//
const ColourRect& RenderedStringParser::getInitialColours() const
{
    return d_initialColours;
}

//----------------------------------------------------------------------------//
void RenderedStringParser::initialiseTagHandlers()
{
    d_tagHandlers[ColourTagName] = &RenderedStringParser::handleColour;
    d_tagHandlers[FontTagName] = &RenderedStringParser::handleFont;
    d_tagHandlers[ImageTagName] = &RenderedStringParser::handleImage;
    d_tagHandlers[WindowTagName] = &RenderedStringParser::handleWindow;
    d_tagHandlers[VertAlignmentTagName] = &RenderedStringParser::handleVertAlignment;
    d_tagHandlers[PaddingTagName] = &RenderedStringParser::handlePadding;
    d_tagHandlers[TopPaddingTagName] = &RenderedStringParser::handleTopPadding;
    d_tagHandlers[BottomPaddingTagName] = &RenderedStringParser::handleBottomPadding;
    d_tagHandlers[LeftPaddingTagName] = &RenderedStringParser::handleLeftPadding;
    d_tagHandlers[RightPaddingTagName] = &RenderedStringParser::handleRightPadding;
    d_tagHandlers[AspectLockTagName] = &RenderedStringParser::handleAspectLock;
    d_tagHandlers[ImageSizeTagName] = &RenderedStringParser::handleImageSize;
    d_tagHandlers[ImageWidthTagName] = &RenderedStringParser::handleImageWidth;
    d_tagHandlers[ImageHeightTagName] = &RenderedStringParser::handleImageHeight;

    d_initialised = true;
}

//----------------------------------------------------------------------------//
void RenderedStringParser::handleColour(RenderedString& rs, const String& value)
{
    d_colours.setColours(PropertyHelper::stringToColour(value));
}

//----------------------------------------------------------------------------//
void RenderedStringParser::handleFont(RenderedString& rs, const String& value)
{
    d_fontName = value;
}

//----------------------------------------------------------------------------//
void RenderedStringParser::handleImage(RenderedString& rs, const String& value)
{
    RenderedStringImageComponent ric(
        PropertyHelper::stringToImage(value));
    ric.setPadding(d_padding);
    ric.setColours(d_colours);
    ric.setVerticalFormatting(d_vertAlignment);
    ric.setSize(d_imageSize);
    ric.setAspectLock(d_aspectLock);
    rs.appendComponent(ric);
}

//----------------------------------------------------------------------------//
void RenderedStringParser::handleWindow(RenderedString& rs, const String& value)
{
    RenderedStringWidgetComponent rwc(value);
    rwc.setPadding(d_padding);
    rwc.setVerticalFormatting(d_vertAlignment);
    rwc.setAspectLock(d_aspectLock);
    rs.appendComponent(rwc);
}

//----------------------------------------------------------------------------//
void RenderedStringParser::handleVertAlignment(RenderedString& rs, const String& value)
{
    if (value == TopAlignedValueName)
        d_vertAlignment = VF_TOP_ALIGNED;
    else if (value == BottomAlignedValueName)
        d_vertAlignment = VF_BOTTOM_ALIGNED;
    else if (value == CentreAlignedValueName)
        d_vertAlignment = VF_CENTRE_ALIGNED;
    else if (value == StretchAlignedValueName)
        d_vertAlignment = VF_STRETCHED;
    else
        hgeLogger::getSingleton().logEvent(
            "RenderedStringParser::handleVertAlignment: unknown "
            "vertical alignment '" + value + "'.  Ignoring!");
}

//----------------------------------------------------------------------------//
void RenderedStringParser::handlePadding(RenderedString& rs,
                                              const String& value)
{
    d_padding = PropertyHelper::stringToRect(value);
}

//----------------------------------------------------------------------------//
void RenderedStringParser::handleTopPadding(RenderedString& rs,
                                                 const String& value)
{
    d_padding.d_top = PropertyHelper::stringToFloat(value);
}

//----------------------------------------------------------------------------//
void RenderedStringParser::handleBottomPadding(RenderedString& rs,
                                                    const String& value)
{
    d_padding.d_bottom = PropertyHelper::stringToFloat(value);
}

//----------------------------------------------------------------------------//
void RenderedStringParser::handleLeftPadding(RenderedString& rs,
                                                  const String& value)
{
    d_padding.d_left = PropertyHelper::stringToFloat(value);
}

//----------------------------------------------------------------------------//
void RenderedStringParser::handleRightPadding(RenderedString& rs,
                                                   const String& value)
{
    d_padding.d_right = PropertyHelper::stringToFloat(value);
}

//----------------------------------------------------------------------------//
void RenderedStringParser::handleAspectLock(RenderedString& rs,
                                                 const String& value)
{
    d_aspectLock = PropertyHelper::stringToBool(value);
}

//----------------------------------------------------------------------------//
void RenderedStringParser::handleImageSize(RenderedString& rs,
                                                const String& value)
{
    d_imageSize = PropertyHelper::stringToSize(value);
}

//----------------------------------------------------------------------------//
void RenderedStringParser::handleImageWidth(RenderedString& rs,
                                                 const String& value)
{
    d_imageSize.d_x = PropertyHelper::stringToFloat(value);
}

//----------------------------------------------------------------------------//
void RenderedStringParser::handleImageHeight(RenderedString& rs,
                                                  const String& value)
{
    d_imageSize.d_y = PropertyHelper::stringToFloat(value);
}